package com.lipaluma.example.clients.test.service;

import static org.junit.Assert.assertNotNull;

import java.util.Arrays;
import java.util.List;

import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.annotation.Transactional;

import com.lipaluma.example.clients.dao.IAccountDAO;
import com.lipaluma.example.clients.dao.IOrderDAO;
import com.lipaluma.example.clients.model.LineOrder;
import com.lipaluma.example.clients.model.Order;
import com.lipaluma.example.clients.model.Order.StatusEnum;
import com.lipaluma.example.clients.service.OrderService;
import com.lipaluma.test.db.SpringJUnit4WithDBClassRunner;
import com.lipaluma.test.db.annotations.assertions.AssertColumns;
import com.lipaluma.test.db.annotations.batch.Batch;
import com.lipaluma.test.db.annotations.clean.CleanSchema;
import com.lipaluma.test.db.internal.DialectEnum;
import com.lipaluma.test.db.internal.assertions.AssertColumnUtils;
import com.lipaluma.test.db.internal.assertions.ColumnType;

@RunWith(SpringJUnit4WithDBClassRunner.class)
@ContextConfiguration("classpath:applicationContext-service.xml")
@CleanSchema(dialect=DialectEnum.H2)
@Transactional
public class OrderServiceTestIntegration {

	@Autowired
	private OrderService orderService;

	@Autowired
	private IOrderDAO orderDAO;
	
	@Autowired
	private IAccountDAO accountDAO;

	/**
	 * Test that check directly in database insertions done during the test.
	 */
	@Test
	@Batch({"classpath:sql/Accounts.sql"})
	@AssertColumns.List({
		@AssertColumns(expected={"14.95", "CREATED"}, request="select totalAmount, status from t_order where id = :order_id"),
		@AssertColumns(expected={"2", "14.95"}, request="select count(*), sum(totalAmount) from t_line_order where order_id = :order_id"),
		@AssertColumns(expected={"1"}, request="select count(*) from t_account acc left join t_order o on o.account_id = acc.id where acc.id = 1")
	})
	public void testCreateOrder() {
		Order order = orderService.createNewOrder(1L, Arrays.asList(createLineOrder(1.99, 5), createLineOrder(2.5, 2)));
		AssertColumnUtils.defineValueForKey(":order_id", order.getId());
	}

	/**
	 * BUG hibernate !! avec LineOrder sans L'objet Order et 
	 * Order.lines qui utilise @JoinColumn(name="order_id")
	 * 
	 * La même configuration sans transactional marche !!
	 * Cela implique qu'en mode transactionnel, durant l'insert de l'Order et des LineOrder, 
	 * il doit perdre la transaction : 
	 * 
	 * Du style
	 * 1) Création Transaction
	 * 2) insertion Order
	 * 3) Création d'une nouvelle connexion : BUG (il aurait dû reprendre la même avec la transaction en cours)
	 * 4) insertion des LineOrder avec Order non connu (normal puisque pas la même connection : donc, order_id de LineOrder = 0. J'ai vérifié cette valeur en Debug)
	 * 5) Test sur les LineOrder qui plante !!
	 * 
	 * En mode Non-transactional : 
	 * 1) insertion Order : l'insertion sera commité direct !!
	 * 2) Création d'une nouvelle connexion : plus de problème, l'order est commité
	 * 3) insertion des LineOrder avec Order connu
	 * 4) Test marche !!
	 */
	@Ignore
	@Test
	@Batch({"classpath:sql/Accounts.sql"})
	@AssertColumns.List({
		@AssertColumns(expected={"14.95", "CREATED"}, request="select totalAmount, status from t_order where id = :order_id"),
		@AssertColumns(expected={"2", "14.95"}, request="select count(*), sum(totalAmount) from t_line_order where order_id = :order_id"),
		@AssertColumns(expected={"1"}, request="select count(*) from t_account acc left join t_order o on o.account_id = acc.id where acc.id = 1")
	})
	public void testCreateOrder2WithHibernateBug() {
		List<LineOrder> lines = Arrays.asList(createLineOrder(1.99, 5), createLineOrder(2.5, 2));
		
		// reprise du code du service !!
		Order order = new Order();
		order.setAccount(accountDAO.getById(1L));
		order.setLines(lines);
		order.setStatus(StatusEnum.CREATED);
		double totalAmount = 0.0;
		for (LineOrder line : lines) {
			totalAmount += line.getTotalAmount();
			// line.setOrder(order);
		}
		order.setTotalAmount(totalAmount);
		orderDAO.persist(order);
		AssertColumnUtils.defineValueForKey(":order_id", order.getId());
	}
	
	/**
	 * Other example that do the same test for a second order. But in this case, we push the order id as a parameter called :order_id.
	 * On the execution of the request, the parameter will be replaced by the value affected during the test.
	 */
	@Test
	@Batch({"classpath:sql/Accounts.sql"})
	@AssertColumns.List({
		@AssertColumns(expected={"12.37", "CREATED"}, request="select round(totalAmount, 2), status from t_order where id = :order_id"),
		@AssertColumns(expected={"3", "12.37"}, request="select count(*), round(sum(totalAmount), 2) from t_line_order where order_id = :order_id", type={ColumnType.INT, ColumnType.DOUBLE}),
		@AssertColumns(expected={"1"}, request="select count(*) from t_account acc left join t_order o on o.account_id = acc.id where acc.id = 2", type=ColumnType.INT)
	})
	public void testCreateSecondOrder() {
		Order order = orderService.createNewOrder(2L, Arrays.asList(createLineOrder(3.25, 2), createLineOrder(1.29, 3), createLineOrder(2, 1)));
		assertNotNull(order.getId());
		AssertColumnUtils.defineValueForKey(":order_id", order.getId());
	}

	private LineOrder createLineOrder(String libelle, double price, int quantity) {
		LineOrder line = new LineOrder();
		line.setLibelleProduct(libelle);
		line.setProductUnitAmount(price);
		line.setQuantity(quantity);
		line.setTotalAmount(quantity * price);
		return line;
	}

	private LineOrder createLineOrder(double price, int quantity) {
		return createLineOrder("libelle produit", price, quantity);
	}
}
